package cn.seaboot.commons.core;

import cn.seaboot.commons.lang.Null;
import cn.seaboot.commons.lang.Symbol;
import org.jetbrains.annotations.NotNull;

import java.util.function.Function;

/**
 * 数据脱敏工具
 *
 * @author Mr.css
 * @version 2023-06-29 14:48
 */
public class DataMaskingUtils {

    /**
     * 默认的密码加密结果
     */
    public static final String DEF_FIXED_PASSWORD6 = "******";

    /**
     * 脱名成定长字符串
     * <p>
     * 前后各保留 n 个字符不脱敏，中间用定长的脱敏字符替换，
     * <p>
     * 这种脱敏方式，脱敏后的数据长度完全一致，方便数据库保存。
     * <p>
     * 例：350****1234
     *
     * @param str   字符串
     * @param front 前置保留 n 个字符不脱敏
     * @param back  后置保留 n 个字符不脱敏
     * @param len   中间脱敏成固定长度的字符
     * @param ch    替换字符
     * @return 结果
     */
    public static String replaceFixedInner(@NotNull String str, int front, int back, int len, char ch) {
        StringBuilder ret = new StringBuilder(str.length());
        for (int i = 0; i < front; i++) {
            ret.append(str.charAt(i));
        }
        for (int i = 0; i < len; i++) {
            ret.append(ch);
        }
        int l = str.length();
        for (int i = l - back; i < l; i++) {
            ret.append(str.charAt(i));
        }
        return ret.toString();
    }


    /**
     * 前后各保留 n 个字符不脱敏，例：350135****1234
     *
     * @param str   字符串
     * @param front 前置保留 n 个字符不脱敏
     * @param back  后置保留 n 个字符不脱敏
     * @param ch    替换字符
     * @return 结果
     */
    public static String replaceInner(@NotNull String str, int front, int back, char ch) {
        return replaceInnerRange(str, front, str.length() - back, ch);
    }


    /**
     * 前后各脱敏 n 个字符，例：******20010101****
     *
     * @param str   字符串
     * @param front 前置脱敏 n 个字符
     * @param back  后置脱敏 n 个字符
     * @param ch    替换字符
     * @return 结果
     */
    public static String replaceOuter(@NotNull String str, int front, int back, char ch) {
        return replaceOuterRange(str, front, str.length() - back, ch);
    }

    /**
     * 区间脱敏
     * <p>
     * 范围内的字符脱敏，例：350135****1234
     * <p>
     * 范围：左闭右开，end 最大值为字符串长度
     *
     * @param str   字符串
     * @param start 起始位置
     * @param end   结束为止
     * @param ch    替换字符
     * @return 结果
     */
    public static String replaceInnerRange(@NotNull String str, int start, int end, char ch) {
        StringBuilder ret = new StringBuilder(str.length());
        for (int i = 0; i < str.length(); i++) {
            if (i >= start && i < end) {
                ret.append(ch);
            } else {
                ret.append(str.charAt(i));
            }
        }
        return ret.toString();
    }

    /**
     * 反区间脱敏
     * <p>
     * 范围内的字符不脱敏，例：******20010101****
     * <p>
     * 范围：左闭右开，end 最大值为字符串长度
     *
     * @param str   字符串
     * @param start 起始位置
     * @param end   结束为止
     * @param ch    替换字符
     * @return 结果
     */
    public static String replaceOuterRange(@NotNull String str, int start, int end, char ch) {
        StringBuilder ret = new StringBuilder(str.length());
        for (int i = 0; i < str.length(); i++) {
            if (i >= start && i < end) {
                ret.append(str.charAt(i));
            } else {
                ret.append(ch);
            }
        }
        return ret.toString();
    }

    /**
     * 隐藏大小写字母和数字
     * <p>
     * 常用于地址脱敏
     *
     * @param str 字符串
     * @param ch  替换字符
     * @return 结果
     */
    public static String replaceLetterOrDigit(@NotNull String str, char ch) {
        StringBuilder ret = new StringBuilder(str.length());
        for (int i = 0; i < str.length(); i++) {
            char c = str.charAt(i);
            if (Character.isDigit(c) || Character.isLowerCase(c) || Character.isUpperCase(c)) {
                ret.append(ch);
            } else {
                ret.append(c);
            }
        }
        return ret.toString();
    }

    /**
     * 中国姓名，保留名字末位
     *
     * @param name 姓名
     * @return 脱敏之后的数据
     */
    public static String chineseName(String name) {
        return CommonUtils.isEmpty(name) ? name : replaceInnerRange(name, 0, name.length() - 1, Symbol.ASTERISK);
    }

    /**
     * 身份证脱敏，隐藏生日
     * <p>
     * 例：350123********1111
     *
     * @param num 身份证
     * @return 结果
     */
    public static String IDCardNum(String num) {
        return CommonUtils.isEmpty(num) ? num : replaceInner(num, 6, 4, Symbol.ASTERISK);
    }

    /**
     * 固定电话
     * <p>
     * 例：123***22
     *
     * @param num 固定电话
     * @return 脱敏结果
     */
    public static String phone(String num) {
        return CommonUtils.isEmpty(num) ? num : replaceInner(num, 3, 2, Symbol.ASTERISK);
    }

    /**
     * 手机号码
     * <p>
     * 例：123***22
     * 例：123****1234
     *
     * @param num 手机号码
     * @return 脱敏结果
     */
    public static String mobile(String num) {
        if (CommonUtils.isEmpty(num)) {
            return Null.STRING;
        } else if (num.length() < 11) {
            return replaceInner(num, 3, 2, Symbol.ASTERISK);
        } else {
            return replaceInner(num, 3, 4, Symbol.ASTERISK);
        }
    }

    /**
     * 地址脱敏
     * <p>
     * 隐藏字符串中的所有数字和字母
     *
     * @param address 地址
     * @return 脱敏结果
     */
    public static String address(String address) {
        return CommonUtils.isEmpty(address) ? address : replaceLetterOrDigit(address, Symbol.ASTERISK);
    }

    /**
     * 邮箱，前面保留 4 位，末尾保留后缀
     * 例如：1234******@qq.com
     *
     * @param email 邮箱
     * @return 脱敏结果
     */
    public static String email(String email) {
        if (StringUtils.isEmpty(email)) {
            return email;
        } else {
            int index = email.indexOf('@');
            if (index < 0) {
                // 错误格式的邮箱
                return email;
            } else {
                return replaceInnerRange(email, 4, index, Symbol.ASTERISK);
            }
        }
    }

    /**
     * 密码
     *
     * @param password 密码
     * @return 脱敏结果
     */
    public static String password(String password) {
        if (CommonUtils.isEmpty(password)) {
            return password;
        } else {
            int len = password.length();
            StringBuilder sb = new StringBuilder(len);
            for (int i = 0; i < len; i++) {
                sb.append(Symbol.ASTERISK);
            }
            return sb.toString();
        }
    }

    /**
     * 定长 6 位密码
     *
     * @param password 密码
     * @return 脱敏结果
     */
    public static String password6(String password) {
        if (CommonUtils.isEmpty(password)) {
            return password;
        } else {
            return DEF_FIXED_PASSWORD6;
        }
    }

    /**
     * 车牌
     * <p>
     * 例：闽A2***W
     *
     * @param num 车牌号
     * @return 脱敏结果
     */
    public static String plateNum(String num) {
        if (StringUtils.isEmpty(num)) {
            return num;
        } else {
            return replaceInnerRange(num, 3, num.length() - 1, Symbol.ASTERISK);
        }
    }

    /**
     * 银行卡
     * <p>
     * 例：622123*********1496
     *
     * @param no 银行卡号
     * @return 脱敏结果
     */
    public static String bankCard(String no) {
        return CommonUtils.isEmpty(no) ? no : replaceInner(no, 6, 4, Symbol.ASTERISK);
    }


    // 下面代码用于定制复杂的脱敏规则
    // -----------------------------------------------------------------------------------------------------


    /**
     * 隐藏大小写字母和数字
     * <p>
     * 常用于地址脱敏
     *
     * @param str 字符串
     * @param fun 字符替换规则
     * @return 结果
     */
    public static String replaceLetterOrDigit(@NotNull String str, Function<Character, Character> fun) {
        StringBuilder ret = new StringBuilder(str.length());
        for (int i = 0; i < str.length(); i++) {
            char c = str.charAt(i);
            if (Character.isDigit(c) || Character.isLowerCase(c) || Character.isHighSurrogate(c)) {
                ret.append(fun.apply(c));
            } else {
                ret.append(c);
            }
        }
        return ret.toString();
    }

    /**
     * 按照范围进行脱敏：指定前后需要保留的字符，对其它内容进行脱敏
     *
     * @param str   字符串
     * @param front 前置保留
     * @param back  后置保留
     * @param fun   字符替换规则
     * @return 结果
     */
    public static String replace(@NotNull String str, int front, int back, Function<Character, Character> fun) {
        return replaceByRange(str, front, str.length() - back, fun);
    }

    /**
     * 按照范围进行脱敏：指定区间内的字符
     * <p>
     * 区间范围：左闭右开，end 最大值为字符串长度
     *
     * @param str   字符串
     * @param start 脱敏起始位置
     * @param end   脱敏结束为止
     * @param fun   字符替换规则
     * @return 结果
     */
    public static String replaceByRange(@NotNull String str, int start, int end, Function<Character, Character> fun) {
        StringBuilder ret = new StringBuilder(str.length());
        for (int i = 0; i < str.length(); i++) {
            if (i < start || i >= end) {
                ret.append(str.charAt(i));
            } else {
                ret.append(fun.apply(str.charAt(i)));
            }
        }
        return ret.toString();
    }
}